"""
Fortran dependency resolver for CodeMCP framework.

This module resolves module dependencies and provides utilities
for understanding Fortran project structure.
"""

import logging
from typing import Dict, List, Any, Set, Optional, Tuple
from pathlib import Path
from collections import defaultdict, deque

from ...core.error_handler import AnalysisError, log_info


class DependencyResolver:
    """
    Fortran dependency resolver.
    
    Resolves module dependencies, finds compilation order,
    and manages project-level analysis.
    """
    
    def __init__(self):
        self.logger = logging.getLogger(__name__)
        self.modules = {}  # module_name -> module_info
        self.files = {}  # file_path -> file_info
        self.dependencies = defaultdict(set)  # module -> set of dependencies
        self.reverse_dependencies = defaultdict(set)  # module -> set of dependents
        self.intrinsic_modules = {
            'iso_c_binding', 'iso_fortran_env', 'ieee_exceptions',
            'ieee_arithmetic', 'ieee_features', 'omp_lib', 'omp_lib_kinds'
        }
    
    def add_file_analysis(self, file_path: str, parsed_data: Dict[str, Any]):
        """
        Add analysis results for a file to the resolver.
        
        Args:
            file_path: Path to the Fortran file
            parsed_data: Parsed file data
        """
        try:
            self.files[file_path] = parsed_data
            
            # Extract modules from this file
            for module_data in parsed_data.get('modules', []):
                module_name = module_data['name']
                self.modules[module_name] = {
                    'name': module_name,
                    'file_path': file_path,
                    'line_number': module_data.get('line_number', 0),
                    'procedures': module_data.get('procedures', []),
                    'uses': module_data.get('uses', [])
                }
                
                # Track dependencies
                for use_module in module_data.get('uses', []):
                    if use_module.lower() not in self.intrinsic_modules:
                        self.dependencies[module_name].add(use_module)
                        self.reverse_dependencies[use_module].add(module_name)
            
            # Also track USE statements at file level
            for use_data in parsed_data.get('uses', []):
                used_module = use_data.get('module', 'unknown')
                if used_module.lower() not in self.intrinsic_modules:
                    # For file-level uses, we track them differently
                    file_key = f"file:{Path(file_path).stem}"
                    self.dependencies[file_key].add(used_module)
                    self.reverse_dependencies[used_module].add(file_key)
            
        except Exception as e:
            self.logger.error(f"Error adding file analysis: {e}")
            raise AnalysisError(f"Failed to add file analysis: {e}")
    
    def resolve_dependencies(self) -> Dict[str, Any]:
        """
        Resolve all dependencies and compute resolution order.
        
        Returns:
            Dictionary with dependency resolution results
        """
        try:
            log_info("Resolving Fortran dependencies")
            
            # Find missing modules
            missing_modules = self._find_missing_modules()
            
            # Detect circular dependencies
            cycles = self._detect_cycles()
            
            # Compute compilation order
            compilation_order = self._compute_compilation_order()
            
            # Find strongly connected components
            sccs = self._find_strongly_connected_components()
            
            result = {
                'modules': self.modules,
                'files': {path: self._summarize_file(data) for path, data in self.files.items()},
                'dependencies': {mod: list(deps) for mod, deps in self.dependencies.items()},
                'reverse_dependencies': {mod: list(deps) for mod, deps in self.reverse_dependencies.items()},
                'missing_modules': list(missing_modules),
                'circular_dependencies': cycles,
                'compilation_order': compilation_order,
                'strongly_connected_components': sccs,
                'statistics': self._compute_resolution_statistics()
            }
            
            log_info(f"Dependency resolution complete: "
                    f"{len(self.modules)} modules, "
                    f"{len(missing_modules)} missing, "
                    f"{len(cycles)} cycles")
            
            return result
            
        except Exception as e:
            self.logger.error(f"Error resolving dependencies: {e}")
            raise AnalysisError(f"Dependency resolution failed: {e}")
    
    def _find_missing_modules(self) -> Set[str]:
        """Find modules that are used but not defined."""
        missing = set()
        
        for module_deps in self.dependencies.values():
            for dep in module_deps:
                if (dep not in self.modules and 
                    dep.lower() not in self.intrinsic_modules and
                    not dep.startswith('file:')):
                    missing.add(dep)
        
        return missing
    
    def _detect_cycles(self) -> List[List[str]]:
        """Detect circular dependencies using DFS."""
        cycles = []
        visited = set()
        rec_stack = set()
        path = []
        
        def dfs(module: str) -> bool:
            if module.startswith('file:') or module.lower() in self.intrinsic_modules:
                return False
                
            if module in rec_stack:
                # Found a cycle
                if module in path:
                    cycle_start = path.index(module)
                    cycle = path[cycle_start:] + [module]
                    cycles.append(cycle)
                return True
            
            if module in visited:
                return False
            
            visited.add(module)
            rec_stack.add(module)
            path.append(module)
            
            for dep in self.dependencies.get(module, []):
                if dfs(dep):
                    break  # Stop at first cycle found in this path
            
            rec_stack.remove(module)
            path.pop()
            return False
        
        for module in self.modules:
            if module not in visited:
                dfs(module)
        
        return cycles
    
    def _compute_compilation_order(self) -> List[str]:
        """Compute topological sort for compilation order."""
        in_degree = defaultdict(int)
        
        # Initialize in-degrees
        for module in self.modules:
            in_degree[module] = 0
        
        # Count incoming edges
        for module, deps in self.dependencies.items():
            if module in self.modules:  # Only count module-to-module dependencies
                for dep in deps:
                    if dep in self.modules:
                        in_degree[module] += 1
        
        # Kahn's algorithm for topological sort
        queue = deque([module for module in self.modules if in_degree[module] == 0])
        result = []
        
        while queue:
            module = queue.popleft()
            result.append(module)
            
            # Reduce in-degree of dependent modules
            for dependent in self.reverse_dependencies.get(module, []):
                if dependent in self.modules:
                    in_degree[dependent] -= 1
                    if in_degree[dependent] == 0:
                        queue.append(dependent)
        
        return result
    
    def _find_strongly_connected_components(self) -> List[List[str]]:
        """Find strongly connected components using Tarjan's algorithm."""
        index_counter = [0]
        stack = []
        lowlinks = {}
        index = {}
        on_stack = {}
        sccs = []
        
        def strongconnect(module: str):
            index[module] = index_counter[0]
            lowlinks[module] = index_counter[0]
            index_counter[0] += 1
            stack.append(module)
            on_stack[module] = True
            
            for dep in self.dependencies.get(module, []):
                if dep in self.modules:  # Only consider defined modules
                    if dep not in index:
                        strongconnect(dep)
                        lowlinks[module] = min(lowlinks[module], lowlinks[dep])
                    elif on_stack.get(dep, False):
                        lowlinks[module] = min(lowlinks[module], index[dep])
            
            if lowlinks[module] == index[module]:
                component = []
                while True:
                    w = stack.pop()
                    on_stack[w] = False
                    component.append(w)
                    if w == module:
                        break
                sccs.append(component)
        
        for module in self.modules:
            if module not in index:
                strongconnect(module)
        
        return sccs
    
    def _summarize_file(self, file_data: Dict[str, Any]) -> Dict[str, Any]:
        """Create a summary of file information."""
        return {
            'filename': file_data.get('filename', 'unknown'),
            'modules': [mod['name'] for mod in file_data.get('modules', [])],
            'functions': len(file_data.get('functions', [])),
            'subroutines': len(file_data.get('subroutines', [])),
            'uses': len(file_data.get('uses', [])),
            'format': file_data.get('format', 'unknown')
        }
    
    def _compute_resolution_statistics(self) -> Dict[str, Any]:
        """Compute dependency resolution statistics."""
        total_modules = len(self.modules)
        total_files = len(self.files)
        total_dependencies = sum(len(deps) for deps in self.dependencies.values())
        
        # Count modules by dependency count
        no_deps = len([m for m, deps in self.dependencies.items() 
                      if not deps and m in self.modules])
        
        return {
            'total_modules': total_modules,
            'total_files': total_files,
            'total_dependencies': total_dependencies,
            'modules_without_dependencies': no_deps,
            'average_dependencies': total_dependencies / total_modules if total_modules > 0 else 0,
            'missing_modules': len(self._find_missing_modules())
        }
    
    def get_module_info(self, module_name: str) -> Optional[Dict[str, Any]]:
        """Get detailed information about a specific module."""
        if module_name not in self.modules:
            return None
        
        module_info = self.modules[module_name].copy()
        module_info['dependencies'] = list(self.dependencies.get(module_name, []))
        module_info['dependents'] = list(self.reverse_dependencies.get(module_name, []))
        
        return module_info
    
    def find_compilation_issues(self) -> List[Dict[str, Any]]:
        """Find potential compilation issues."""
        issues = []
        
        # Check for missing modules
        missing = self._find_missing_modules()
        if missing:
            issues.append({
                'type': 'missing_modules',
                'severity': 'error',
                'message': f"Missing module definitions: {', '.join(missing)}",
                'modules': list(missing)
            })
        
        # Check for circular dependencies
        cycles = self._detect_cycles()
        if cycles:
            issues.append({
                'type': 'circular_dependencies',
                'severity': 'error',
                'message': f"Found {len(cycles)} circular dependency cycles",
                'cycles': cycles
            })
        
        return issues